#!/bin/sh -ef
#
# Copyright (C) 2003-2019  Dmitry V. Levin <ldv@altlinux.org>
# Copyright (C) 2007  Alex V. Myltsev <avm@altlinux.org>
# 
# The chroot cache functions for the hsh-initroot
#
# SPDX-License-Identifier: GPL-2.0-or-later
#

rebuild_chroot_cache=
def_cache_compress=lz4

archive_chroot_cache_compress_file()
{
	local dest_archive err_file
	dest_archive="$1"; shift
	err_file="$cache_dir/chroot/err"

	rm -f "$err_file"

	trap 'rm -f -- "$dest_archive"' EXIT HUP INT QUIT TERM

	{ "$@" < /dev/null ||
	  > "$err_file"; } |
		{ $compressor_prog > "$dest_archive" ||
		  > "$err_file"; }

	trap - EXIT HUP INT QUIT TERM

	if [ -f "$err_file" ]; then
		rm -f "$err_file" "$dest_archive"
		return 1
	fi
	[ -s "$dest_archive" ] || {
		rm -f -- "$dest_archive"
		fatal "$dest_archive: Cowardly refusing to create an empty archive"
	}
	return 0
}

archive_chroot_cache()
{
	[ -n "$rebuild_chroot_cache" ] || return 0

	local cookie_file="$cache_dir/chroot/cookie"
	local aptbox_archive="$cache_dir/chroot/aptbox.tar"
	local rpmdb_archive="$cache_dir/chroot/rpmdb.tar"
	local chroot_archive="$cache_dir/chroot/chroot.cpio"
	local compressor_used="$cache_dir/chroot/compressor"
	local compressor_prog="${cache_compress:-$def_cache_compress}"

	# rpmdb
	archive_chroot_cache_compress_file "$rpmdb_archive" \
	tar -c -C aptbox/var/lib/rpm/ . &&
		verbose 'RPM database archivation complete.' ||
		fatal 'RPM database archivation failed.'

	# chroot
	create_entry_fakeroot_header
	cat >>"$entry" <<__EOF__
cd /
find --version >/dev/null 2>&1 && find=find || find=/.host/find
cpio --version >/dev/null 2>&1 && cpio=cpio || cpio=/.host/cpio
\$find * ! -path dev ! -path dev/\\* |\$cpio --create --quiet --format=newc
__EOF__

	wlimit_time_elapsed=$wlimit_time_long \
	wlimit_time_idle=$wlimit_time_long \
	archive_chroot_cache_compress_file "$chroot_archive" \
	chrootuid1 &&
		verbose 'Chroot archivation complete.' ||
		fatal 'Chroot archivation failed.'

	# aptbox
	if [ -n "$unchecked_initroot_cache" ]; then
		archive_chroot_cache_compress_file "$aptbox_archive" \
		tar -c -C aptbox --exclude='./var/lib/rpm/*' . &&
		printf %s "$unchecked_initroot_cache" > "$cookie_file" &&
			verbose 'aptbox archivation complete.' ||
			fatal 'aptbox archivation failed.'
	fi

	printf %s "$compressor_prog" >"$compressor_used"

	# Regenerate data for later cache validation.
	mv -f $verbose "$cache_dir"/chroot/list{.new,}/init >&2
	mv -f $verbose "$cache_dir"/chroot/list{.new,}/build >&2
	rm -rf "$cache_dir"/chroot/package
	mv -f $verbose "$cache_dir"/chroot/package{.new,} >&2
	mv -f $verbose "$cache_dir"/chroot/fakeroot-options{.new,} >&2
} # archive_chroot_cache

check_chroot_cache()
{
	[ -z "$no_cache" ] || return 0

	local f n
	local aptbox_archive="$cache_dir/chroot/aptbox.tar"
	local cookie_file="$cache_dir/chroot/cookie"
	local bad="$cache_dir/chroot/list.new/bad"
	rm -f "$bad"
	cat "$cache_dir"/chroot/list.new/{init,build} |
		while read f; do
			[ -n "$f" ] || continue
			n="${f##*/}"
			if ! touch -r "$f" "$cache_dir/chroot/package.new/$n"; then
				# Don't break early, show the whole list of missing RPMs.
				printf %s\\n "$n" >>"$bad"
				#break
			fi
		done
	[ ! -f "$bad" ] || fatal 'Failed to check chroot cache.'

	printf %s "$save_fakeroot" >"$cache_dir/chroot/fakeroot-options.new"

	for f in "$cache_dir"/chroot/{compressor,rpmdb.tar,chroot.cpio}; do
		if [ ! -s "$f" ]; then
			verbose "Missing $f file, invalidating chroot cache."
			rebuild_chroot_cache=1
			return
		fi
	done

	if ! cmp -s "$cache_dir"/chroot/fakeroot-options{.new,}; then
		verbose 'Fakeroot options changed, invalidating chroot cache.'
		rebuild_chroot_cache=1
		return
	fi

	if [ -n "$unchecked_initroot_cache" ] &&
	   [ -s "$cookie_file" -a -s "$aptbox_archive" ] &&
	   [ "$unchecked_initroot_cache" = "$(cat -- "$cookie_file")" ]; then
		# Lists are not used if initroot cache is unchecked.
		return
	fi

	if ! cmp -s "$cache_dir"/chroot/list{.new,}/init; then
		verbose 'Init list changed, invalidating chroot cache.'
		rebuild_chroot_cache=1
		return
	fi

	if ! cmp -s "$cache_dir"/chroot/list{.new,}/build; then
		verbose 'Build list changed, invalidating chroot cache.'
		rebuild_chroot_cache=1
		return
	fi

	rm -f "$bad"
	cat "$cache_dir"/chroot/list.new/{init,build} |
		while read f; do
			n="${f##*/}"
			if [ "$cache_dir/chroot/package.new/$n" -nt "$cache_dir/chroot/package/$n" -o \
			     "$cache_dir/chroot/package.new/$n" -ot "$cache_dir/chroot/package/$n" ]; then
				verbose "package $n changed, invalidating chroot cache"
				printf %s\\n "$n" > "$bad"
				break
			fi
		done
	if [ -f "$bad" ]; then
		rebuild_chroot_cache=1
		return
	fi
} # check_chroot_cache

unpack_chroot_cache()
{
	local compressor_prog
	local cookie_file="$cache_dir/chroot/cookie"
	compressor_prog=$(cat "$cache_dir/chroot/compressor")

	local f

	# rpmdb
	f="$cache_dir/chroot/rpmdb.tar"
	find aptbox/var/lib/rpm/ -mindepth 1 -depth -delete
	$compressor_prog -d <"$f" |
	tar -x -C aptbox/var/lib/rpm/ &&
		verbose "Unpacked $f." ||
		fatal "Unpack of $f failed."

	# chroot
	f="$cache_dir/chroot/chroot.cpio"
	$compressor_prog -d <"$f" |
	wlimit_time_elapsed=$wlimit_time_long wlimit_time_idle=$wlimit_time_long \
	    chrootuid1 /.host/cpio --extract --make-directories --sparse --quiet --preserve-modification-time &&
		verbose "Unpacked $f." ||
		fatal "Unpack of $f failed."
	if [ -n "$save_fakeroot" ]; then
		wlimit_time_elapsed=$wlimit_time_short wlimit_time_idle=$wlimit_time_short \
		    chrootuid1 /bin/touch /.fakedata </dev/null &&
			verbose "Created fakedata." ||
			fatal "Failed to create fakedata."
		create_entry_fakeroot_header
		cat >>"$entry" <<__EOF__
cd /
cpio --extract --make-directories --sparse --quiet --unconditional --preserve-modification-time
__EOF__
		$compressor_prog -d <"$f" |
		wlimit_time_elapsed=$wlimit_time_long wlimit_time_idle=$wlimit_time_long \
		    chrootuid1 &&
			verbose "Unpacked $f for fakedata." ||
			fatal "Unpack of $f for fakedata failed."
	fi
	create_entry_fakeroot_header
	cat >>"$entry" <<__EOF__
if grep -qs ^rooter: /etc/passwd 2>/dev/null; then
	userdel rooter
	groupadd -g $gid1 rooter
	useradd -M -u $uid1 -g $gid1 -d /root rooter
fi
if grep -qs ^builder: /etc/passwd 2>/dev/null; then
	userdel builder
	groupadd -g $gid2 builder
	useradd -M -u $uid2 -g $gid2 -d /usr/src builder
	chmod $verbose 1777 /usr/src >&2
fi
__EOF__

	wlimit_time_elapsed=$wlimit_time_short wlimit_time_idle=$wlimit_time_short wlimit_bytes_written=$wlimit_bytes_out \
	    chrootuid1 </dev/null &&
		verbose 'First time initialization complete.' ||
		fatal 'First time initialization failed.'

	f="$cache_dir/chroot/aptbox.tar"
	if [ -n "$unchecked_initroot_cache" ]; then
		if [ ! -s "$f" ] ||
		   [ "$unchecked_initroot_cache" != "$(cat -- "$cookie_file")" ]; then
			archive_chroot_cache_compress_file "$f" \
			tar -c -C aptbox --exclude='./var/lib/rpm/*' . &&
			printf %s "$unchecked_initroot_cache" > "$cookie_file" &&
				verbose 'aptbox archivation complete.' ||
				fatal 'aptbox archivation failed.'
		fi
	fi
} # unpack_chroot_cache

create_chroot()
{
	copy_chroot_incoming $initlist

	local f
	for f in $initlist; do
		rpm2cpio "$f" |wlimit_time_elapsed=$wlimit_time_long wlimit_time_idle=$wlimit_time_long \
		    chrootuid1 /.host/cpio --extract --make-directories --sparse --quiet --preserve-modification-time &&
			verbose "Unpacked ${f##*/}." ||
			fatal "Unpack of ${f##*/} failed."
	done
	verbose 'Unpacked initial package list.'
	if [ -n "$save_fakeroot" ]; then
		wlimit_time_elapsed=$wlimit_time_short wlimit_time_idle=$wlimit_time_short \
		    chrootuid1 /bin/touch /.fakedata </dev/null &&
			verbose "Created fakedata." ||
			fatal "Failed to create fakedata."
		create_entry_fakeroot_header
		cat >>"$entry" <<__EOF__
cd /
cpio --extract --make-directories --sparse --quiet --unconditional --preserve-modification-time
__EOF__
		for f in $initlist; do
			rpm2cpio "$f" |wlimit_time_elapsed=$wlimit_time_long wlimit_time_idle=$wlimit_time_long \
			    chrootuid1 &&
				verbose "Unpacked ${f##*/} for fakedata." ||
				fatal "Unpack of ${f##*/} for fakedata failed."
		done
	fi

	create_entry_fakeroot_header
	cat >>"$entry" <<__EOF__
# WORKAROUND: mtab is missing in setup-2.2.2-alt1
touch /etc/mtab
# /WORKAROUND
# WORKAROUND: filesystem < 2.1.7-alt1 doesn't provide /sys
mkdir -p /sys
# /WORKAROUND
rm -f /etc/rpm/macros.db1
# WORKAROUND: glibc-locales is too large
echo '%_install_langs ${install_langs:-$def_install_langs}' >>/etc/rpm/macros
# Force the treatment of scriptlet errors as expected in install checks in Girar
echo '%_rpmscript_werror 1' >>/etc/rpm/macros
# /WORKAROUND
rpmdb --initdb
__EOF__
	verbose "Created entry point: $entry"

	wlimit_time_elapsed=$wlimit_time_short wlimit_time_idle=$wlimit_time_short wlimit_bytes_written=$wlimit_bytes_out \
	    chrootuid1 </dev/null &&
		verbose 'Created RPM database.' ||
		fatal 'Failed to create RPM database.'

	create_entry_fakeroot_header
	cat >>"$entry" <<__EOF__
export DURING_INSTALL=1
${exclude_docs:+export RPM_EXCLUDEDOCS=1}
${rpmi:-$def_rpmi} -i $verbose --ignorearch ${exclude_docs:+--excludedocs} --justdb --nodeps $(for f in $initlist; do printf %s "\"$(quote_shell "${f##*/}")\" "; done)
__EOF__

	wlimit_time_elapsed=$wlimit_time_long wlimit_time_idle=$wlimit_time_short \
	    chrootuid1 </dev/null &&
		verbose 'Installed initial package list.' ||
		fatal 'Failed to install initial package list.'

	purge_chroot_in
	update_RPM_database --nodeps --force $initlist

	if [ -n "$install_resolver_configuration_files" ]; then
		for f in /etc/host.conf /etc/hosts /etc/resolv.conf; do
			if [ -r "$f" -a -s "$f" ]; then
				create_entry_fakeroot_header
				cat >>"$entry" <<__EOF__
cat >$f
__EOF__
				wlimit_time_elapsed=$wlimit_time_short wlimit_time_idle=$wlimit_time_short wlimit_bytes_written=$wlimit_bytes_out \
				    chrootuid1 <"$f" &&
					verbose "Installed $f file." ||
					fatal "Failed to install $f file."
			fi
		done
	fi

	# At this stage, RPM inside chroot is fully functional.

	if [ -n "$buildlist" ]; then
		copy_chroot_incoming $buildlist

		create_entry_fakeroot_header
		cat >>"$entry" <<__EOF__
set -f
export DURING_INSTALL=1
${exclude_docs:+export RPM_EXCLUDEDOCS=1}
# Exclude packages which should not be installed
inst_list="$(for f in $buildlist; do
	f="${f##*/}"
	for p in $noinstall_pattern_list; do
		if [ -z "${f##$p}" ]; then
			continue 2
		fi
	done
	printf %s "$(quote_shell "$f") "
done)"
noinst_list="$(for f in $buildlist; do
	f="${f##*/}"
	for p in $noinstall_pattern_list; do
		if [ -z "${f##$p}" ]; then
			printf %s "$(quote_shell "$f") "
			continue 2
		fi
	done
done)"
[ -z "\$noinst_list" ] || rpm_nodeps="--nodeps"
[ -z "\$inst_list" ] || ${rpmi:-$def_rpmi} -i --ignorearch ${exclude_docs:+--excludedocs} \$rpm_nodeps \$inst_list
[ -z "\$noinst_list" ] || ${rpmi:-$def_rpmi} -i --ignorearch ${exclude_docs:+--excludedocs} --nodeps --justdb \$noinst_list
__EOF__

		wlimit_time_elapsed=$wlimit_time_long wlimit_time_idle=$wlimit_time_short \
		    chrootuid1 </dev/null &&
			verbose 'Installed build package list.' ||
			fatal 'Failed to install build package list.'

		purge_chroot_in
		update_RPM_database --force $buildlist

		# First time scripts.

		create_entry_fakeroot_header
		cat >>"$entry" <<__EOF__
ldconfig
rpmdb --rebuilddb
if type adjust_kernel_headers >/dev/null 2>&1; then
	adjust_kernel_headers
fi
if [ -x /.host/postin ]; then
	/.host/postin
fi
groupadd -g $gid0 caller ||:
useradd -M -u $uid0 -g $gid0 -d / caller ||:
groupadd -g $gid1 rooter
useradd -M -u $uid1 -g $gid1 -d /root rooter
groupadd -g $gid2 builder
useradd -M -u $uid2 -g $gid2 -d /usr/src builder
chmod $verbose 1777 /usr/src >&2
__EOF__

		wlimit_time_elapsed=$wlimit_time_short wlimit_time_idle=$wlimit_time_short wlimit_bytes_written=$wlimit_bytes_out \
		    chrootuid1 </dev/null &&
			verbose 'First time initialization complete.' ||
			fatal 'First time initialization failed.'
	fi # $buildlist

	archive_chroot_cache
} # create_chroot
